<?php
/**
 * @link https://www.kancloud.cn/cleverstone/ymb2
 * @copyright Copyright (c) 2020 Yii Manager Software LLC
 */

namespace backend\controllers;

use builder\base\BaseController;
use builder\form\FormBuilder;
use builder\helper\App;
use builder\helper\H5;
use builder\helper\Str;
use builder\helper\T;
use builder\tree\TreeBuilder;
use common\models\Admin;
use common\models\SystemAuthRelation;
use common\models\SystemAuthRelationService;
use common\models\SystemGroup;
use common\models\SystemGroupService;

/**
 * 管理组
 * @author cleverstone
 * @since ym2.0
 */
class GroupController extends BaseController
{
    //锁定权限，防止冲突
    const LOCK_PERMISSION_KEY = 'permission_locking';

    public $actionVerbs = [
        'index' => ['GET'],
        'create' => ['GET', 'POST'],
        'update' => ['GET', 'POST'],
        'delete' => ['POST'],
        'dispatch' => ['GET', 'POST'],
    ];

    /**
     * 管理组列表
     * @return string
     * @throws \builder\base\InvalidInstanceException
     * @throws \builder\base\NotFoundParamsException
     * @throws \yii\base\InvalidConfigException
     * @throws \yii\di\NotInstantiableException
     */
    public function actionIndex()
    {
        $viewBuilder = $this->viewBuilder;

        $treeTable = $viewBuilder->getTreeTable(['id' => 'Group_Index']);
        if ($this->isAjax) {
            $treeTable->setPage(false)
                ->setQuery(function () {
                    return SystemGroupService::tree();
                })
                ->setTtColumns([
                    'id',
                    'name',
                    'Desc_Str' => function ($row) {
                        return $row['desc'] ?: '-';
                    },
                    'Assigned_Str' => function($row) {
                        return H5::badgeDot($row['assigned'] ? '已授权' : '未授权', $row['assigned'] ? 'primary' : 'gray1');
                    },
                    'Status_Str' => function ($row) {
                        return H5::badgeDot($row['status'] ? '正常' : '禁用', $row['status'] ? 'success' : 'red');
                    },
                    'created_at',
                    'updated_at',
                ]);

        } else {
            App::setViewBreadcrumb([
                ['name' => '系统管理'],
                ['name' => '管理组列表', 'url' => 'group/index'],
            ]);
            $treeTable->setTitle('管理组列表')
                ->setPage(false)
                ->setMaxHeight(800)
                ->setTreeTableHead([
                    $this->treeTableHead->field('id')->title('ID'),
                    $this->treeTableHead->field('name')->title('管理组')->minWidth(300),
                    $this->treeTableHead->field('Desc_Str')->title('简介')->width(300),
                    $this->treeTableHead->field('Assigned_Str')->title('权限')->width(100),
                    $this->treeTableHead->field('Status_Str')->title('当前状态')->width(100)->help('请注意：\n1、冻结管理组时，将冻结管理组和其子管理组。\n2、解冻管理组时，将解冻管理组和其父管理组。\n3、新增或更新管理员账号时, 被冻结的管理组则无法选取！'),
                    $this->treeTableHead->field('created_at')->title('添加日期')->width(180),
                    $this->treeTableHead->field('updated_at')->title('上次更新')->width(180),
                    $this->treeTableHead->toolbar()->title('操作项')->fixedNone()->width(280),
                ])
                ->setTreeViewOption([
                    'expandAllDefault' => true,
                    'showIcon' => false,
                ])
                ->setTtRowOperation([
                    $this->ttRowOperation->modal()->title('分配权限')->route('group/dispatch')->width(450)->height(735),
                    $this->ttRowOperation->modal()->title('新增管理组')->route('group/create')->width(700)->height(430),
                    $this->ttRowOperation->modal()->title('编 辑')->route('group/update')->width(730)->height(510),
                    $this->ttRowOperation->ajax()->title('<span class="text-danger">删 除</span>')->route('group/delete')->method('POST'),
                ])
                ->setTtToolbarCustom([
                    $this->ttToolbarCustom->modal('新增管理组')->btnClass('btn-primary')->title('新 增')->route('group/create')->width(700)->height(430),
                ])
                ->setTtToolbarExpandAll()
                ->setTtToolbarRefresh();
        }

        return $viewBuilder->render();
    }

    /**
     * 新增
     * @param null|int $id 管理组ID
     * @return \yii\web\Response|string
     * @throws \Throwable
     */
    public function actionCreate($id = null)
    {
        if ($this->isPost) {
            $bodyParams = $this->filteredPost;
            $model = new SystemGroup();
            $model->setScenario('SCE_Group_Add');
            $model->load($bodyParams);
            if ($model->pid) {
                // 检查父组是否允许创建子分组。
                $parentModel = SystemGroup::findOne($model->pid);
                if (!$parentModel->isAllowCreateChild()) {
                    return $this->asFail('管理组【' . $parentModel->name . '】禁止创建子分组（原因：管理组未授权或已禁用）。');
                }
                $parentModel = SystemGroup::findOne($model->pid);
                $model->path = $parentModel->path . $parentModel->id . '-';
            } else {
                $model->path = '-0-';
            }
            if ($model->save()) {
                return $this->asOk('添加管理组【'.$model->name.'】成功。');
            }

            return $this->asFail($model->error);
        } else {
            return FormBuilder::instance(['id' => 'Group_Create'])
                ->setTitle('新增')
                ->setPartial()
                ->setFormControl([
                    'pid' => $this->formControl->hidden()->defaultValue($id ?: 0),
                    'name' => $this->formControl->text()->label('管理组')->required()->placeholder('请填写管理组名')->maxLength(50),
                    'status' => $this->formControl->sw()->label('当前状态')->required()->title('启用', '禁用')->checked(),
                    'desc' => $this->formControl->textarea()->label('简介')->maxLength(255),
                ])
                ->setResetBtn('清 空')
                ->setSubmitBtn()
                ->setAutoClose()
                ->setSubmittedCalls(['parent' => ['Group_Index']])
                ->render();
        }
    }

    /**
     * 编辑
     * @param int $id 管理组ID
     * @return \yii\web\Response|string
     * @throws \Throwable
     */
    public function actionUpdate($id)
    {
        $model = SystemGroup::findOne($id);
        if ($this->isPost) {
            if (empty($model)) {
                return $this->asFail('ID:【'. $id .'】错误数据不存在。');
            }

            // 标记原数据
            $oldStatus = $model->status;
            $oldPid = $model->pid;

            // 表单数据校验
            $bodyParams = $this->post;
            $model->setScenario('SCE_Group_Update');
            $model->load($bodyParams);
            if (!$model->validate()) {
                return $this->asFail($model->error);
            }

            // 父分组的合法性校验
            $pid = $model->pid;
            if ($pid == $model->id) {
                return $this->asFail('选择的父管理组不能是自身#' . $model->id);
            }

            // 如果父分组变更，根据pid重新生成path。
            if ($oldPid != $pid) {
                if ($pid == 0) {
                    $model->path = '-0-';
                } else {
                    $parentGroup = SystemGroup::findOne($pid);
                    if (!$parentGroup->isAllowCreateChild()) {
                        return $this->asFail('选择的父管理组已被禁用#' . $model->id);
                    }
                    $model->path = $parentGroup->path . $parentGroup->id . '-';
                }
            }

            $trans = $this->db->beginTransaction();
            try {
                // 保存管理组数据变更
                $model->save(false);

                $extraMsg = '。';
                if ($oldStatus != $model->status) {
                    // 冻结其子管理组
                    if ($model->status == SystemGroup::STATUS_DENY) {
                        SystemGroup::updateAll(['status' => $model->status,], ['like', 'path', "-{$model->id}-"]);
                        $extraMsg = '，更新管理组和其子管理组状态为【冻结】。';
                    }
                    // 解冻其父管理组
                    if ($model->status == SystemGroup::STATUS_NORMAL) {
                        $pathArray = array_filter(explode('-', $model->path));
                        if (!empty($pathArray)) {
                            SystemGroup::updateAll(['status' => $model->status,], ['in', 'id', $pathArray]);
                            $extraMsg = '，更新管理组和其父管理组状态为【解冻】。';
                        }
                    }
                }

                $trans->commit();
                return $this->asOk('编辑管理组【'.$model->name.'】成功' . $extraMsg);
            } catch (\Throwable $e) {
                $trans->rollBack();
                return $this->asFail($e->getMessage() . "#{$model->id}");
            }

        } else {
            if (empty($model)) {
                return $this->renderContentX(H5::alert('ID错误数据不存在。'), 'error');
            }

            $groupTree = SystemGroupService::tree([], true);
            array_unshift($groupTree, [
                'id' => 0,
                'name' => '拥有者',
                'children' => [],
            ]);
            return FormBuilder::instance(['id' => 'Group_Update'])
                ->setTitle('编辑')
                ->setPartial()
                ->setFormControl([
                    'pid' => $this->formControl->xselectTree()->label('所属父管理组')->tips('请选择所属父管理组')->radio()->strict(false)->filterable()->clickClose()->expandedKeys(true)->required()->defaultValue($model->pid)->data($groupTree)->prop(['value' => 'id']),
                    'name' => $this->formControl->text()->label('管理组')->required()->placeholder('请填写管理组名')->defaultValue($model->name),
                    'status' => $this->formControl->sw()->label('当前状态')->required()->title('启用', '禁用')->checked(!!$model->status),
                    'desc' => $this->formControl->textarea()->label('简介')->defaultValue($model->desc),
                ])
                ->setResetBtn()
                ->setSubmitBtn()
                ->setAutoClose()
                ->setSubmittedCalls(['parent' => ['Group_Index']])
                ->render();
        }
    }

    /**
     * 删除
     * @return \yii\web\Response
     * @throws \Throwable
     * @throws \yii\db\StaleObjectException
     */
    public function actionDelete()
    {
        //表单数据校验
        $bodyParams = $this->filteredPost;
        if (empty($bodyParams['id'])) {
            return $this->asOk('参数ID缺失。');
        }

        //校验ID是否正确
        $id = $bodyParams['id'];
        $model = SystemGroup::findOne($id);
        if (empty($model)) {
            return $this->asFail('ID【'.$id.'】错误数据不存在。');
        }

        //存在子管理组，无法删除
        $children = SystemGroup::query()->where(['like', 'path', "-{$id}-"])->one();
        if (!empty($children)) {
            return $this->asFail('请删除子分组后重新操作。');
        }

        //当前管理组已分配管理员账号，无法删除
        $user = Admin::findOne(['group' => $id]);
        if (!empty($user)) {
            return $this->asFail('管理组【'.$model->name.'】存在账号占用无法删除');
        }

        $trans = $this->db->beginTransaction();
        try {
            //删除管理组
            $model->delete();

            //删除管理组权限
            SystemAuthRelation::deleteAll(['group_id' => $model->id]);

            //删除管理组权限缓存
            \Yii::$app->rbacManager->invalidateCachePermissions($model->id);

            $trans->commit();
            return $this->asOk('删除管理组【'.$model->name.'】成功，其权限以及缓存已被清除。');
        } catch (\Throwable $e) {
            $trans->rollBack();
            return $this->asFail('删除管理组【'.$model->name.'】失败；' . $e->getMessage());
        }
    }

    /**
     * 权限
     * @param int $id 管理组ID
     * @return \yii\web\Response|string
     * @throws \Throwable
     */
    public function actionDispatch($id)
    {
        $groupModel = SystemGroup::findOne($id);

        if ($this->isPost) {
            //请求参数校验
            $bodyParams = $this->filteredPost;
            if (empty($bodyParams['id'])) {
                return $this->asFail('参数ID缺失。');
            }

            $menuIds = $bodyParams['id'];
            $originalPermissions = SystemAuthRelation::query('menu_id')->where(['group_id' => $id])->column();
            //得到需要删除的权限
            $toBeDeleted = array_diff($originalPermissions, $menuIds);
            //得到需要新增的权限
            $toBeAdded = array_diff($menuIds, $originalPermissions);
            if (empty($toBeDeleted) && empty($toBeAdded)) {
                return $this->asOk('管理组【'.$groupModel->name.'】没有权限变更，操作完成。');
            }

            //检查权限操作是否锁定
            $permissionLock = $this->cache->get(self::LOCK_PERMISSION_KEY);
            if ($permissionLock) {
                return $this->asFail('其他账号权限操作中，请稍后重试。');
            }

            //锁定权限操作
            $lockVersion = Str::random();
            $this->cache->set(self::LOCK_PERMISSION_KEY, $lockVersion);

            $trans = $this->db->beginTransaction();
            try {
                //删除当前管理组的不在的权限
                SystemAuthRelation::deleteAll(['group_id' => $id, 'menu_id' => $toBeDeleted]);

                //为当前管理组新增新的权限
                $now = T::now();
                $columns = [];
                foreach ($toBeAdded as $itemID) {
                    $columns[] = [$id, $itemID, $now];
                }
                $this->db->createCommand()->batchInsert(SystemAuthRelation::tableName(), ['group_id', 'menu_id', 'created_at'], $columns)->execute();

                //标记授权状态
                if (!$groupModel->assigned) {
                    $groupModel->assigned = 1;
                    $groupModel->save(false);
                }

                //检查更新版本是否一致
                $lockVersionCache = $this->cache->get(self::LOCK_PERMISSION_KEY);
                if (strcmp($lockVersion, $lockVersionCache)) {
                    throw new \Exception('其他账号权限操作中，权限更新失败！请稍后重新尝试...');
                }

                $trans->commit();
                //清除权限缓存
                \Yii::$app->rbacManager->invalidateCachePermissions($id);
                //删除权限锁
                $this->cache->delete(self::LOCK_PERMISSION_KEY);

                return $this->asOk('管理组【'.$groupModel->name.'】分配权限成功');
            } catch (\Throwable $e) {
                $trans->rollBack();
                //删除权限锁
                $lockVersionCache = $this->cache->get(self::LOCK_PERMISSION_KEY);
                if (!strcmp($lockVersion, $lockVersionCache)) {
                    $this->cache->delete(self::LOCK_PERMISSION_KEY);
                }

                return $this->asFail($e->getMessage());
            }

        } else {
            $tree = TreeBuilder::instance(['id' => 'Group_Dispatch']);
            if ($this->isAjax) {
                $tree->setData(function () use ($groupModel) {
                    return SystemAuthRelationService::permissionTree($groupModel->id, Admin::ADMIN_GROUP);
                });
            } else {
                $extraCss = <<<'CSS'
                .btn-bottom-group{text-align:right;}
CSS;
                $tree->setTitle('权限')
                    ->setPartial()
                    ->setTreeLayout([
                        'class' => ['h-550', 'overflow-auto'],
                    ])
                    ->setExtraCss($extraCss)
                    ->setCustomName([
                        'title' => 'name',
                    ])
                    ->setSwitchCheckedBtn()
                    ->setSwitchSpreadBtn()
                    ->setOperationItem([
                        $this->operationItem->page()->title('重置')->positionBottom()->btnClass('layui-btn-gray')->targetSelf(),
                        $this->operationItem->ajax()->positionBottom()->title('保存权限')->params(['id'])->closeModal()->questionDescription('确认当前权限分配吗?')->method()->submittedCalls(['parent' => ['Group_Index']]),
                    ]);
            }

            return $tree->render();
        }
    }
}